#/usr/bin/env python

import os, pygame
from math import log
from pyscape_settings import *
from pygame.locals import *



'''
    sqr(3) / 2 = H/W
'''


color_key = (0x80, 0x00, 0x80)

class hex_map:
    
    def __init__(self, screen='none', map='new', tile_dir='.' ):
        """
        hex_map( Screen='none' ): return Screen
        Default will create a fullscreen and return that screen, 
        otherwise the passed in screen is used. 
        """
        if screen == 'none':
            print "making Screen"
            self.screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN)
        else:
            self.screen = screen

        self.render_loc = (0,0)
        self.render_scale = 1
        self._tiles = {}
        self.tiles = {}
        self.map = {}
        self.temp_hex = {}
        self.cursor_tile_pos = (0,0)

        pygame.display.set_caption('Welcome to pyscape')
        self.load_tiles( tile_dir )
        self.load_map( map )

        self.box_rect = pygame.Rect(20,20, self._box_width, self._box_height)
        self.text = "Tile: "

    def pixel_to_tile(self, scn_x, scn_y ):
        """
        Converts a pixel location to a location on the hexagon map.
        """        

        if self.render_loc[1] & 1:
            odd_mod = self.odd_row_x_mod
        else:
            odd_mod = 0
            
        box_x = (scn_x / self.box_width) 
        box_y = (scn_y / self.box_height)

        if self.render_loc[0] != 0 or self.render_loc[1] != 0:
            box_x = (scn_x / self.box_width) + self.render_loc[0]
            box_y = (scn_y / self.box_height) + self.render_loc[1]
        
        box_px_y = scn_y % int(self.box_height)
        box_px_x = scn_x % int(self.box_width)

        # Update the box_rect to show the correct size & location in the grid
        self.box_rect = pygame.Rect( 20, 20, self.box_width, self.box_height )
        self.box_rect.topleft = ( self.box_width * ( box_x - self.render_loc[0]), self.box_height * (box_y - self.render_loc[1] ))
       
        # Discover which hexagon in the box is being selected
        # Find the odd of the displayed version.
        if ( (box_y & 1) and not ( self.render_loc[1] & 1) )  or  ( not(box_y & 1) and ( self.render_loc[1] & 1) ) :
            # Odd rows
            # Use  y = mx + b
            # Where M is the slope of 30 degrees, and b is the triangle height
            if ( (box_px_y < (self._m*box_px_x)  and box_px_x < (self.box_width / 2)) or
               (box_px_y < ((-self._m)*box_px_x) + (2*self.b) and box_px_x > (self.box_width / 2)) ):
                # It's above
                hexMapX=box_x
                hexMapY=box_y - 1
            elif box_px_x > (self.box_width / 2):
                # It's on the right
                hexMapX=box_x 
                hexMapY=box_y
            else:
                # It's on the left
                hexMapX=box_x - 1
                hexMapY=box_y

        else:
            # Even rows
            # Find if the pixel is over the line (really under the line given the coord system) 
            # Use  y = mx + b
            # Where M is the slope of 30 degrees, and b is the triangle height
            if box_px_y < ((self._m)*box_px_x - self.b ):
                # It's up to the left
                hexMapX=box_x
                hexMapY=box_y - 1
            elif box_px_y < ((-self._m)*box_px_x + self.b ):
                # It's up to the right
                hexMapX=box_x - 1
                hexMapY=box_y - 1
            else:
                # It's in the main part of the box
                hexMapX=box_x
                hexMapY=box_y

        tile_text = "Tile: " + str((hexMapX, hexMapY)) 
        b_text = " Box: " + str(( box_x, box_y )) 
        bx_text = " Box Pixel: " + str((box_px_x, box_px_y ))
        mb_text = "B: " + str(self.b)
        self.text = tile_text + b_text + bx_text + mb_text
        return (hexMapX,hexMapY)

    def tile_to_pixel(self,til_x,til_y, img_pixel=True ):
        """
        Returns the top left pixel location of a hexagon map location.
        Note that this location is usually on a different tile.
        By default the background image pixel is returned, for the screen
        pixel set img_pixel to False.
        """

        if img_pixel:
            if til_y & 1:
            # Odd rows will be moved to the right.
                img_x, img_y = ( til_x * self.box_width + self.odd_row_x_mod, til_y * self.box_height )
            else:
                img_x, img_y = ( til_x * self.box_width, til_y * self.box_height )

            return img_x, img_y

        else:
            #if self.render_loc[1] & 1:
            #    odd_mod = self.odd_row_x_mod
            #else:
            #    odd_mod = 0

            # Must convert from background image to displayed image
            if til_y & 1:
                # Odd rows will be moved to the right
                img_x = til_x*self.til_width+self.odd_row_x_mod
                img_y = til_y*self.box_height

            else:
                img_x = til_x*self.til_width
                img_y = til_y*self.box_height

            scn_x = int( (img_x - (self.box_width * self.render_loc[0])) * self.render_scale )
            scn_y = int( (img_y - (self.box_height * self.render_loc[1])) * self.render_scale )
        
            return scn_x, scn_y


    def draw_map( self, til_xy = (0,0), tile_width = 0, map_mode = 0 ):
        """ 
        Draw the map to the screen. 
        til_xy is the upper left corner tile to be displayed, and tile_width is the number of 
        tiles to display to the right.  Display hight is computed based on screen size. 
        Sets the currently stored internal screen of the map to the location requested, scaled 
        as requested.  The internal map is not adjusted.
        map_mode: 0=normal, 1=with elevation
        """

        # Find the conversion factor
        if tile_width != 0:
            self.render_scale = float(self.screen.get_width())  / ( float(self._tile_width) * tile_width )
        else:
            self.render_scale = 1
            #tile_width = int(round( float(self._tile_width) / float(self.screen.get_width())))
            tile_width = int(round( float(self.screen.get_width()) / float(self._tile_width) ))
       
        # Update the render_loc
        self.render_loc = til_xy
    
        # Adjust the parameters used by tile_to_pixel
        self.tile_width = int(round(self._tile_width * self.render_scale))
        self.tile_height = int(round(self._tile_height * self.render_scale))
        self.box_width = self.tile_width
        self.box_height = int(round(self.tile_height * 0.75))
        self.row_height = self.box_height
        self.odd_row_x_mod = int(round(self.tile_width / 2))
        self.b = self.tile_height - self.box_height
        
        # Convert all the images to that size
        for key in self._tiles:
            self.tiles[key] = pygame.transform.scale( self._tiles[key], ( self.tile_width, self.tile_height) )
        # Find the map height
        tile_height =  int(round(float(self.screen.get_height()) / float(self.box_height))) + 1
        # Create the font
        fnt = pygame.font.Font(pygame.font.get_default_font(),12)

        # Prepare to draw elevation map
        low = high = self.map[ (0,0) ][1]
        if map_mode:
            for x in range( self.map[ 'map_size' ][0] ):
                for y in range( self.map[ 'map_size' ][1] ):
                    if low > self.map[ (x,y) ][1]:
                        low = self.map[ (x,y) ][1]
                    if high < self.map[ (x,y) ][1]:
                        high = self.map[ (x,y) ][1]
            for num in range(low,high+1):
                color = num * ( 255 / (high+1 - low) )
                
                int(round( ( num / (high+1 - low) ) * 255 ))
                self.tiles[ num ] = self.get_elevation_surface( (color,color,color), (self.tile_width, self.tile_height) )


        for x in range( til_xy[0], til_xy[0] + tile_width ):
            for y in range( til_xy[1], til_xy[1] + tile_height ):
                pixel_x, pixel_y = self.tile_to_pixel(x - til_xy[0], y - til_xy[1] )
                map_x = x
                map_y = y
                # Blit the map once for the map, 
                # and then again for any temp tiles on top of that, in case of transparency
                self.screen.blit( self.tiles[ self.map[(map_x,map_y)][ 0 ] ], (pixel_x,pixel_y))

                if self.temp_hex.has_key( (map_x, map_y) ):
                    self.screen.blit( self.tiles[ self.temp_hex[ (map_x,map_y)]], (pixel_x,pixel_y))

                if map_mode:
                    self.screen.blit( self.tiles[ self.map[(map_x,map_y)][ 1 ] ], (pixel_x,pixel_y))
        
                # Show the hexagon map location in the center of the tile.
                location = fnt.render("%d,%d" % (x,y), 0, (0xff,0xff,0xff))
                lrect=location.get_rect()
                lrect.center = (pixel_x+(self.tile_width/2),pixel_y+(self.tile_height/2))                
                self.screen.blit(location,lrect.topleft)
    
        #pygame.draw.rect(self.screen, (0xff,0xff,0xff), self.box_rect, 1)


    
    def load_map( self, map='new' ):
        """
        Load or create the map image and place all tiles.
        If map="editor", it is opened all with one tile so as to edit.
        Size is only used in editor mode.
        """
        if map == 'new':
            # Need to have some tiles loaded - which should set the tile_width, etc.
            map_size = (50,50) # Default size
            if self._tile_width:  # and self.tiles.has_key("land"):
                self.map[ "map_size" ] = map_size
                self.map_size_tile = map_size
                self.map_size_pix = ( map_size[0] * self._tile_width, map_size[1] * self._tile_height )
                for x in range(map_size[0]):
                    for y in range(map_size[1]):
                        self.map[(x,y)] = ( "land", 1 )
        else:
            self.map = map
            self.map_size_tile = map[ "map_size" ]
            self.map_size_pix = ( self.map_size_tile[0] * self._tile_width, self.map_size_tile[1] * self._tile_height )


    def get_elevation_surface( self, color, size ):
    
        b = int(round( size[1] / 4.00 ))
        elev_band_sz = ( size[1] / 2, size[1] / 6 )
        s_stripe = pygame.Surface( elev_band_sz, flags=SRCALPHA )
        s_elev = pygame.Surface( size, flags=SRCALPHA )
        
        exp = log( 255.0, elev_band_sz[1] )
    
        for i in range( elev_band_sz[1] ):
            pygame.draw.line(s_stripe, ( color[0], color[1], color[2], int( (255-(i**exp)) ) ), (i/2 , i), (elev_band_sz[0] - i/2 , i))
    
        s_elev.blit( pygame.transform.rotate(s_stripe, 30), (0,0) )
        s_elev.blit( pygame.transform.rotate(s_stripe, 90), (0,b) )
    
        s_stripe_rot = pygame.transform.rotate(s_stripe, 150)
        x = s_elev.get_width() - s_stripe_rot.get_width()
        y = s_elev.get_height() - s_stripe_rot.get_height()
        s_elev.blit( s_stripe_rot, (0,y) )
        
        s_stripe_rot = pygame.transform.rotate(s_stripe, 210)
        x = s_elev.get_width() - s_stripe_rot.get_width()
        y = s_elev.get_height() - s_stripe_rot.get_height()
        s_elev.blit( s_stripe_rot, (x,y) )
        
        s_stripe_rot = pygame.transform.rotate(s_stripe, 270)
        x = s_elev.get_width() - s_stripe_rot.get_width()
        s_elev.blit( s_stripe_rot, (x,b) )
        
        s_stripe_rot = pygame.transform.rotate(s_stripe, 330)
        x = s_elev.get_width() - s_stripe_rot.get_width()
        s_elev.blit( s_stripe_rot, (x,0) )

        return s_elev



    def load_tiles( self, dir ):
        """
        Loads the tiles in the given dir and saves it in a dictinary based on name.
        Tiles are expected to start with hex_ and end with .png, and be the same size.
        Pass in absolute directories.
        """

        # Load all the tiles in the tile directory, they should all be the same size
        for tile in os.listdir(dir):
            if tile.endswith( "png" ) and tile.startswith( "hex_" ):
                new_tile = pygame.image.load( os.path.abspath(dir) +'/' + tile ).convert()
                new_tile.set_colorkey(color_key, RLEACCEL)
                # Cut off png and the hex_
                name = tile.split('.')[0][4:]
                self._tiles[ name ] = new_tile

        # There should be a land hexagon that will be a model for others
        self.cursor = self._tiles[ "land" ]

        a_tile = self.cursor
        self._tile_width = a_tile.get_width()
        self._tile_height = a_tile.get_height()
        self._box_width = self._tile_width
        self._box_height = int(round(self._tile_height * 0.75))
        self._row_height = self._box_height
        self._odd_row_x_mod = int(round(self._tile_width / 2))
        self._b = self._tile_height - self._box_height
        self._m = 0.57735027
        
        if abs( self._tile_width - (self._tile_height * 0.866) ) > (self._tile_height * 0.15):
            print "FYI: not such a great hexagon."
        

    def set_temp_hex( self, (x, y), image ):
        if self.temp_hex.has_key( (x,y) ) == False:
            self.temp_hex[(x,y)] = image


    def rem_temp_hex( self, rem ):
        """
        Remove all instances of the rem, can be key or value.
        """
        if self.temp_hex.has_key( rem ) == True:
            ret = self.temp_hex.pop( rem )

        to_be_rem = []
        for x in self.temp_hex.iterkeys():
            if self.temp_hex[x] == rem:
                to_be_rem.append(x)
       
        for x in to_be_rem:
            ret = self.temp_hex.pop( x )


    def set_cursor( self, x, y ):
        """
        Set the hexagon map cursor. 
        Expects mouse coordinates input.
        """
        x, y = self.pixel_to_tile( x, y )

        self.rem_temp_hex( "cursor" )
        self.set_temp_hex( (x, y), "cursor" )
  

    def set_tile( self, x, y, type, height=1, by_tile=False, save_to_map=True ):
        """  
        Similar to creating a map, updates the tile location image and map.  
        By default input is by pixel location.  If this location is not already in the map it just returns.
        The pixel location passed in should be the screen, the location set to is the background img.
        """
        
        if by_tile == False:
            x, y = self.pixel_to_tile( x, y ) 
        pixel_x, pixel_y = self.tile_to_pixel( x, y, img_pixel = True )

        if self.map.has_key((x,y)) == False:
            print "Error: map doesn't have key: " + str((x,y))
            return

        if save_to_map:
            self.map[(x,y)] = (type, height)

    
    def set_tile_height( self, x, y, height=1, by_tile=False ):
        """  
        Similar to creating a map, updates the tile location image and map.  
        By default input is by pixel location.  If this location is not already in the map it just returns.
        The pixel location passed in should be the screen, the location set to is the background img.
        """
        
        if by_tile == False:
            x, y = self.pixel_to_tile( x, y ) 
        pixel_x, pixel_y = self.tile_to_pixel( x, y, img_pixel = True )

        if self.map.has_key((x,y)) == False:
            print "Error: map doesn't have key: " + str((x,y))
            return

        self.map[(x,y)] = ( self.map[(x,y)][0], height)

